﻿using System;
using System.Collections;
using System.Reflection;
using System.IO;

namespace SuperCalendar
{

    public static class ObjectDumper
    {

        public static String Dump(this object o)
        {
            StringWriter sw = new StringWriter();
            Dump(sw,o, 0, new ArrayList());
            return sw.ToString();
        }

        private static string Pad(int level, string msg, params object[] args)
        {
            string val = String.Format(msg, args);
            return val.PadLeft((level * 4) + val.Length);
        }

        private static void Dump(TextWriter writer,object o, int level, ArrayList previous)
        {
            Type type = null;

            if (o != null)
            {
                type = o.GetType();
            }

            Dump(writer,o, type, null, level, previous);
        }

        private static void Dump(TextWriter writer, object o, Type type, string name, int level, ArrayList previous)
        {
            if (o == null)
            {
                writer.WriteLine(Pad(level, "{0} ({1}): (null)", name, type.Name));
                return;
            }

            if (previous.Contains(o))
            {
                return;
            }

            previous.Add(o);

            if (type.IsPrimitive || o is string)
            {
                DumpPrimitive(writer,o, type, name, level, previous);
            }
            else
            {
                DumpComposite(writer,o, type, name, level, previous);
            }
        }

        private static void DumpPrimitive(TextWriter writer,object o, Type type, string name, int level, ArrayList previous)
        {
            if (name != null)
            {
                writer.WriteLine(Pad(level, "{0} ({1}): {2}", name, type.Name, o));
            }
            else
            {
                writer.WriteLine(Pad(level, "({0}) {1}", type.Name, o));
            }
        }

        private static void DumpComposite(TextWriter writer, object o, Type type, string name, int level, ArrayList previous)
        {

            if (name != null)
            {
                writer.WriteLine(Pad(level, "{0} ({1}):", name, type.Name));
            }
            else
            {
                writer.WriteLine(Pad(level, "({0})", type.Name));
            }

            if (o is IDictionary)
            {
                DumpDictionary(writer,(IDictionary)o, level, previous);
            }
            else if (o is ICollection)
            {
                DumpCollection(writer,(ICollection)o, level, previous);
            }
            else
            {
                MemberInfo[] members = o.GetType().GetMembers(BindingFlags.Instance | BindingFlags.Public |
                                                                BindingFlags.NonPublic);

                foreach (MemberInfo member in members)
                {
                    try
                    {
                        DumpMember(writer,o, member, level, previous);
                    }
                    catch { }
                }
            }
        }

        private static void DumpCollection(TextWriter writer,ICollection collection, int level, ArrayList previous)
        {
            foreach (object child in collection)
            {
                Dump(writer,child, level + 1, previous);
            }
        }

        private static void DumpDictionary(TextWriter writer,IDictionary dictionary, int level, ArrayList previous)
        {
            foreach (object key in dictionary.Keys)
            {
                writer.WriteLine(Pad(level + 1, "[{0}] ({1}):", key, key.GetType().Name));

                Dump(writer,dictionary[key], level + 2, previous);
            }
        }

        private static void DumpMember(TextWriter writer,object o, MemberInfo member, int level, ArrayList previous)
        {
            if (member is MethodInfo || member is ConstructorInfo ||
                member is EventInfo)
                return;

            if (member is FieldInfo)
            {
                FieldInfo field = (FieldInfo)member;

                string name = member.Name;
                if ((field.Attributes & FieldAttributes.Public) == 0)
                {
                    name = "#" + name;
                }

                Dump(writer,field.GetValue(o), field.FieldType, name, level + 1, previous);
            }
            else if (member is PropertyInfo)
            {
                PropertyInfo prop = (PropertyInfo)member;

                if (prop.GetIndexParameters().Length == 0 && prop.CanRead)
                {
                    string name = member.Name;
                    MethodInfo getter = prop.GetGetMethod();

                    if ((getter.Attributes & MethodAttributes.Public) == 0)
                    {
                        name = "#" + name;
                    }

                    Dump(writer,prop.GetValue(o, null), prop.PropertyType, name, level + 1, previous);
                }
            }
        }
    }
}

